package tp.pr5;

import tp.pr5.instructions.exceptions.InstructionExecutionException;
import tp.pr5.items.Item;

public class NavigationModule extends Observable<NavigationObserver>
{
	
	private City aCity;
	private Place initialPlace;
	private Direction direction;
	
	/**
	 * Navigation module constructor. It needs the city map and the initial place
	 * 
	 * @param aCity
	 * @param initialPlace
	 */
	
	public NavigationModule(City aCity, Place initialPlace)
	{
		this.aCity = aCity;
		this.initialPlace = initialPlace;
	}
	
	/**
	 * Checks if the robot has arrived at a spaceship
	 * 
	 * @return true if an only if the current place is the spaceship
	 */
	
	public boolean atSpaceship()
	{
		return this.initialPlace.isSpaceship();
	}
	
	/**
	 * Updates the current direction of the robot according to the rotation
	 * 
	 * @param rotation
	 */
	
	public void rotate(Rotation rotation)
	{

		if(rotation.equals(Rotation.LEFT))
		{
			this.direction = this.direction.rotateLeft();
			this.emitHeadingChange();
		}
		if(rotation.equals(Rotation.RIGHT))
		{
			this.direction = this.direction.rotateRight();
			this.emitHeadingChange();
		}
	}
	
	/** 
	 * Returns the street opposite the robot
	 * 
	 * @return The street which the robot is facing, or null, if there is not any street in this direction
	 */
	
	public Direction getCurrentHeading()
	{
		return this.direction;
	}
	
	/**
	 * The method tries to move the robot following the current direction. 
	 * If the movement is not possible because there is no street, 
	 * or there is a street which is closed, then it throws an exception.
	 * Otherwise the current place is updated according to the movement
	 *  
	 * @throws InstructionExecutionException An exception with a message about the encountered problem
	 */
	
	public void move() throws InstructionExecutionException
	{
		try {
			
			Street street = this.getHeadingStreet();
			
			//Check if the streets exists in the map
			if(street != null)
			{
				Place nextPlace = street.nextPlace(initialPlace);
				
				//Checks if the street is open  
				if(street.isOpen())
				{	
					this.initialPlace = nextPlace;
					this.emitRobotArrivesAtPlace(initialPlace, direction);
				} 
				else 
				{
					throw new InstructionExecutionException(Constants.MESSAGE_DOOR_CLOSED.replace("{ID}", this.direction.toString()));
				}
			}
			else 
			{
				throw new InstructionExecutionException(Constants.MESSAGE_NO_PLACE.replace("{ID}", this.direction.toString()));
			}
			
		}catch (Exception e) {
			throw e;
		}
			
		
	}
	
	/**
	 * Tries to pick an item characterized by a given identifier from the current place. 
	 * If the action was completed the item is removed from the current place.
	 * 
	 * @param id The name of the item to be dropped.
	 * @return the item picked
	 */
	
	public Item pickItemFromCurrentPlace(String id)
	{
		Item item = initialPlace.pickItem(id);
		this.emitPlaceHasChange();
		
		return item;
	}
	
	/**
	 * Drop an item in the current place. It assumes that there is no other item with the same name/id there. Otherwise, the behaviour is undefined.
	 * 
	 * @param it The name of the item to be dropped.
	 */
	
	public void dropItemAtCurrentPlace(Item it)
	{
		this.initialPlace.addItem(it);
		this.emitPlaceHasChange();
	}
	
	/**
	 * Checks if there is an item with a given id in this place
	 * 
	 * @param id  Identifier of the item we are looking for
	 * @return true if and only if an item with this id is in the current place
	 */
	
	public boolean findItemAtCurrentPlace(String id)
	{
		return this.initialPlace.existItem(id);
	}
	
	/**
	 * Initializes the current heading according to the parameter
	 * 
	 * @param heading New direction for the robot
	 */
	
	public void initHeading(Direction heading)
	{
		this.direction = heading;
		this.emitInitNavigationModule();
	}
	
	/**
	 * Prints the information (description + inventory) of the current place
	 * 
	 */
	
	public void scanCurrentPlace()
	{
		this.emitPlaceScanned();
	}
	
	/**
	 * Returns the robot heading
	 * 
	 * @return The street where the robot is facing to.
	 */
	
	public Street getHeadingStreet()
	{
		return this.aCity.lookForStreet(initialPlace, direction);
	}
	
	/**
	 * Returns the current place where the robot is (for testing purposes)
	 * 
	 * @return The current place
	 */
	
	public Place getCurrentPlace()
	{
		return this.initialPlace;
	}
	
	/**
	 * Notifies to all observers that heading has changed
	 * 
	 */
	public void emitHeadingChange()
	{
		for (NavigationObserver nObserver : this.lista)
		{
			nObserver.headingChanged(direction);
		}
	}
	
	/**
	 * 
	 * @param initialPlace place of the robot when arrives at a place
	 * @param direction of the robot
	 */
	public void emitRobotArrivesAtPlace(PlaceInfo initialPlace, Direction direction)
	{
		for (NavigationObserver nObserver : this.lista)
		{
			nObserver.robotArrivesAtPlace(direction, initialPlace);
		}
	}
	
	/**
	 * 
	 * 
	 */
	public void emitPlaceScanned()
	{
		for (NavigationObserver nObserver : this.lista)
		{
			nObserver.placeScanned(initialPlace);
		}
	}
	
	/**
	 * 
	 * 
	 */
	public void emitPlaceHasChange()
	{
		for (NavigationObserver nObserver : this.lista)
		{
			nObserver.placeHasChanged(initialPlace);
		}
	}
	
	/**
	 * 
	 * 
	 */
	public void emitInitNavigationModule()
	{
		for (NavigationObserver nObserver : this.lista)
		{
			nObserver.initNavigationModule(initialPlace, direction);
		}
	}
	
}
